use super::error::BlockResult;

use crate::{byte::*, lit::LiteralSection, seq::{SeqSection, SeqTuple}, workspace::{WorkSpace, OutputWorkSpace}};

#[derive(Debug)]
pub struct CompressedBlock {
    literal_section:LiteralSection,
    seq_section:SeqSection,
}

impl CompressedBlock {
    pub fn serialize_into<W:ByteWrite>(&self,writer:&mut ByteWriter<W>) -> BlockResult<()> {
        self.literal_section.serialize_into(writer)?;
        self.seq_section.serialize_into(writer)?;
        Ok(())
    }

    pub fn deserialize_from<R:ByteRead>(total_len:u32,reader:&mut ByteReader<R>, workspace:&mut WorkSpace) -> BlockResult<Self> {
        let mut rest_len = total_len;
        let literal_section = LiteralSection::deserialize_from(&mut rest_len, reader, workspace)?;
        let seq_section = SeqSection::deserialize_from(&mut rest_len, reader, workspace)?;
        Ok(Self { literal_section, seq_section })
    }

    pub fn uncompressed_to<O:OutputStream>(&self, output:&mut O, workspace:&mut OutputWorkSpace) -> OutputResult<usize>  {
        let literal_stream = self.literal_stream();
        let sequences = self.sequences();
        let mut decoded_data = Vec::new();
        let mut read_pointer = 0;
        let mut offset_hitstory = OffsetHistory::new();

        for seq in sequences {
            let literals_length = seq.literals_length as usize;
            decoded_data.append(&mut literal_stream[read_pointer..read_pointer + literals_length].to_vec());
            read_pointer += literals_length;

            let match_length = seq.match_length as usize;
            let offset = offset_hitstory.update(seq);
            Self::extend_from_matching(&mut decoded_data, match_length, offset, workspace);
        }
        if read_pointer < literal_stream.len() {
            decoded_data.append(&mut literal_stream[read_pointer..].to_vec());
        }
        Ok(output.write_from(&decoded_data)?)
    }

    pub fn literal_stream(&self) -> &[u8] {
        self.literal_section.literal_stream()
    }
    
    pub fn sequences(&self) -> &[SeqTuple] {
        self.seq_section.seq_tuples()
    }

    fn extend_from_matching(decoded_data:&mut Vec<u8>, match_length:usize, offset:usize, workspace:&mut OutputWorkSpace) {
        if offset <= decoded_data.len() {
            if match_length < offset {
                let start = decoded_data.len() - offset;
                let end = start + match_length;
                let mut match_vec = decoded_data[start..end].to_vec();
                decoded_data.append(&mut match_vec);
            } else {
                let times = match_length / offset;
                let rest_length = match_length % offset;
                let repeat_start = decoded_data.len() - offset;
                let mut repeat_part = decoded_data[repeat_start..].to_vec();
                let rest_start = repeat_start;
                let rest_end = repeat_start + rest_length;
                let mut rest_part = decoded_data[rest_start..rest_end].to_vec();
                (0..times).for_each(|_|decoded_data.append(&mut repeat_part));
                decoded_data.append(&mut rest_part);
            }
        } else {
            let dict_size = offset - decoded_data.len();
            let dict_content = workspace.dict_content();
            if match_length <= dict_size {
                let dict_start = dict_content.len() - dict_size;
                let dict_end = dict_start + match_length;
                let mut dict_data = dict_content[dict_start..dict_end].to_vec();
                decoded_data.append(&mut dict_data);
            } else {
                let dict_start = dict_content.len() - dict_size;
                let mut dict_data = dict_content[dict_start..].to_vec();
                decoded_data.append(&mut dict_data);
                let new_offset = decoded_data.len();
                let new_match_length = match_length - dict_size;
                Self::extend_from_matching(decoded_data, new_match_length, new_offset, workspace);
            }
        }
    }
}

pub struct OffsetHistory {
    history:[usize;3],
}

impl OffsetHistory {
    pub fn new() -> Self {
        Self { history:[1,4,8] }
    }

    pub fn update(&mut self, seq:&SeqTuple) -> usize {
        let literals_length = seq.literals_length as usize;
        let offset_value = seq.offset_value as usize;
            
        let actual_offset = if literals_length > 0 {
            match offset_value {
                1..=3 => self.history[offset_value as usize - 1],
                _ => {
                    //new offset
                    offset_value - 3
                }
            }
        } else {
            match offset_value {
                1..=2 => self.history[offset_value as usize],
                3 => self.history[0] - 1,
                _ => {
                    //new offset
                    offset_value - 3
                }
            }
        };

        //update history
        if literals_length > 0 {
            match offset_value {
                1 => {
                    //nothing
                }
                2 => {
                    self.history[1] = self.history[0];
                    self.history[0] = actual_offset;
                }
                _ => {
                    self.history[2] = self.history[1];
                    self.history[1] = self.history[0];
                    self.history[0] = actual_offset;
                }
            }
        } else {
            match offset_value {
                1 => {
                    self.history[1] = self.history[0];
                    self.history[0] = actual_offset;
                }
                2 => {
                    self.history[2] = self.history[1];
                    self.history[1] = self.history[0];
                    self.history[0] = actual_offset;
                }
                _ => {
                    self.history[2] = self.history[1];
                    self.history[1] = self.history[0];
                    self.history[0] = actual_offset;
                }
            }
        }

        actual_offset
    }
}